From [my blog post](http://rystraum.com/nested-forms-with-polymorphic-association-in-active-adminformtastic/).

Given models:   

* `Invoice`, `has_many :items`
* `Item`, `belongs_to :itemizable, polymorphic: true`
* `Domain` & `Service`, `has_many :items, as: :itemizable`

The problem was multiple things:

1. The automagic of Formtastic can’t detect the collection if it’s a polymorphic association
2. Formtastic doesn’t really play well with non-existent attributes

Initially, I’ve thought of just doing:

```ruby
  ActiveAdmin.register Invoice do
    form do |f|
      # ...
      f.has_many :items do |item|
        item.input :itemizable, collection: (Domain.all + Service.all)
        item.input :quantity
        item.input :price_per_piece
      end

      f.actions
    end
  end
```

But this fails because   
1) domains and service can share the same id and    
2) I have no way to tell what the item was.   

A few hours in and I was going nowhere. It’s surprisingly hard to look for anything related to polymorphic associations on Formtastic. [This post](http://stackoverflow.com/questions/6856851/adding-a-custom-input-field-in-formtastic) gave me an idea however.

So, I’ve thought, why not just hold the id temporarily on an accessor attribute and just do the assignment from a callback before validation kicks in based on which attribute it went into? Raise an error if both were filled up.

It worked! I can now save new polymorphic records. (look at Item#assign_itemizable)

There’s a small problem however. The form to edit an existing record doesn’t pre-populate the corresponding select dropdowns. The solutions was rather simple, override the reader method to return the id of the itemizable if the itemizable is a member of the class.

Maintenance-wise, everything here would add overhead for every new itemizable model I would associate to item, but overall, I think it was a pretty elegant hack. *pats self at back*

Here’s the complete code:

```ruby
  # app/models/invoice.rb
  class Invoice < ActiveRecord::Base 
    has_many :items

    accepts_nested_attributes_for :items
  end

  # app/models/item.rb
  class Item < ActiveRecord::Base
    before_validation :assign_itemizable

    belongs_to :invoice
    belongs_to :itemizable, polymorphic: true
    
    validates :itemizable, presence: true

    attr_accessor :itemizable_domain, :itemizable_service

    def itemizable_domain
      self.itemizable.id if self.itemizable.is_a? Domain
    end

    def itemizable_service
      self.itemizable.id if self.itemizable.is_a? Service
    end

    protected
    def assign_itemizable
      if !@itemizable_domain.blank? && !@itemizable_service.blank?
        errors.add(:itemizable, "can't have both a domain and a service") 
      else
        unless @itemizable_domain.blank?
          self.itemizable = Domain.find(@itemizable_domain)
        end

        unless @itemizable_service.blank?
          self.itemizable = Service.find(@itemizable_service)
        end
      end
    end
  end

  # app/admin/invoice.rb
  ActiveAdmin.register Invoice do
    form do |f|
      f.inputs "Invoice" do 
        f.input :customer
        f.input :invoice_number
        f.input :issuing_person
        f.input :issued_on
        f.input :remarks
      end
      
      f.has_many :items do |item|
        item.input :itemizable_domain, collection: Domain.all
        item.input :itemizable_service, collection: Service.all
        item.input :quantity
        item.input :price_per_piece
      end

      f.actions
    end
  end
```


### Other possibility would be

```ruby
  ActiveAdmin.register Invoice do
    form do |f|
      # ...
      f.has_many :items do |item|
        item.input :itemizable_identifier, collection: (Domain.all + Service.all).map { |i| [ i.name, "#{i.class.to_s}-#{i.id}"] }
        item.input :quantity
        item.input :price_per_piece
      end

      f.actions
    end
  end
```

```ruby
  # app/models/item.rb
  class Item < ActiveRecord::Base

    ...

    belongs_to :itemizable, polymorphic: true

    validates :itemizable, presence: true

    ...

    def itemizable_identifier
      "#{itemizable_type}-#{itemizable_id}" if itemizable_type.present? && itemizable_id.present?
    end

    def itemizable_identifier=(itemizable_data)
      if itemizable_data.present?
        itemizable_data = itemizable_data.split('-')
        self.itemizable_type = itemizable_data[0]
        self.itemizable_id = itemizable_data[1]
      end
    end

```

### Other possibility with a twist and support for UUID

```ruby
  ActiveAdmin.register Invoice do
    form do |f|
      # ...
      f.has_many :items do |item|
        item.input :itemizable_identifier, collection: (Domain.all + Service.all).map { |i| [ i.name, "#{i.class}-#{i.id}"] }
        item.input :quantity
        item.input :price_per_piece
      end

      f.actions
    end
  end
```

```ruby
  # app/models/item.rb
  class Item < ActiveRecord::Base

    ...

    belongs_to :itemizable, polymorphic: true

    validates :itemizable, presence: true

    ...

    attr_accessible :itemizable_identifier

    def itemizable_identifier
      "#{itemizable.class}-#{itemizable.id}"
    end

    def itemizable_identifier=(itemizable_data)
      return unless itemizable_data.present?
      match = itemizable_data.match(/^(?<itemizable_type>Domain|Service)-(?<itemizable_id>.*)$/)
      return unless match
      self.itemizable_id = match[:itemizable_id]
      self.itemizable_type = match[:itemizable_type]
    end

```

### Create polymorphic child with custom form

I needed to create any posible polymorphic one-to-one child, each with it's different sub-form, here's what i came with.

```ruby
   # app/models/item.rb
  class Item < ActiveRecord::Base

    ...

    belongs_to :itemizable, polymorphic: true

    accepts_nested_attributes_for :itemizable

    ITEMIZABLE_TYPES = %w(Domain Service)

    def build_itemizable(params)
      raise "Unknown itemizable_type: #{itemizable_type}" unless ITEMIZABLE_TYPES.include?(itemizable_type)
      self.itemizable = itemizable_type.constantize.new(params)
    end
```

```scss
   # app/assets/stylesheets/active_admin.css.scss
   .polyform {display: none}
```

```coffee
   # app/assets/javascripts/active_admin.js.coffee
    ready = ->
      $(".polyselect").on "change", ->
        $('.polyform').hide()
        $('#' + $(@).val() + '_poly').show()
    
      $('.polyform').first().parent('form').on "submit", (e) ->
        $('.polyform').each (index, element) =>
          $e = $(element)
          if $e.css('display') != 'block'
            $e.remove()
    
    $(document).ready(ready)
    $(document).on('page:load', ready)
```

```ruby
    # app/admin/item.rb

   permit_params :itemizable_type, :itemizable_id, itemizable_attributes: [:all, :posible, :permitted, :fields]

   form do |f|

   ...

     f.inputs 'Type' do
       f.input :itemizable_type, input_html: {class: 'polyselect'},
         collection: Item::ITEMIZABLE_TYPES
     end
 
     f.inputs 'Domain', for: [:itemizable, f.object.itemizable || Domain.new],
       id: 'Domain_poly', class: 'inputs polyform' do |fc|
       fc.input :this
       fc.input :posible
       fc.input :child
       fc.input :fields
    end

    ...
    end

   controller do
     def create
       @tramo = Tramo.new permitted_params[:tramo]
       if @tramo.save
         redirect_to collection_path
       end
     end
   end
